package fr.utt.lo02.CrazyEights.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Observable;

import fr.utt.lo02.CrazyEights.model.cards.Card;
import fr.utt.lo02.CrazyEights.model.cards.CardRank;
import fr.utt.lo02.CrazyEights.model.exceptions.EmptyPileException;
import fr.utt.lo02.CrazyEights.model.piles.DiscardPile;
import fr.utt.lo02.CrazyEights.model.piles.StockPile;
import fr.utt.lo02.CrazyEights.model.players.AI;
import fr.utt.lo02.CrazyEights.model.players.Player;
import fr.utt.lo02.CrazyEights.model.players.PlayersList;
import fr.utt.lo02.CrazyEights.view.DiscardPilePanel;
import fr.utt.lo02.CrazyEights.view.MainWindow;
import fr.utt.lo02.CrazyEights.view.Console;
import fr.utt.lo02.CrazyEights.view.PlayerPanel;
import fr.utt.lo02.CrazyEights.view.StockPilePanel;


/**
 * Main class which represents the game.
 * @author Jeremy
 *
 */
public class Game extends Observable {
	
	/**
	 * Game singleton
	 */
	private static Game game;
	
	/**
	 * stockPile
	 */
	private StockPile stockPile;
	
	/**
	 * DiscardPile
	 */
	private DiscardPile discardPile;
	
	/**
	 * The players list
	 */
	private PlayersList playersList;
	
	/**
	 * Number of players in the game
	 */
	private int nbOfPlayers = 2;
	
	/**
	 * Number of cards by player
	 */
	private int nbOfCardsByPlayer = 8;
	
	/**
	 * End method
	 */
	private EndMethod end = EndMethod.Points;
	
	/**
	 * The target number of points or rounds
	 */
	private int target = 10;
	
	/**
	 * If we have to change the suit it is set to true else it is false
	 */
	private boolean changeSuit = false;
	
	/**
	 * GUI Panel for the discardPile
	 */
	private DiscardPilePanel discardPilePanel;
	
	/**
	 * GUI Panel for stockPile
	 */
	private StockPilePanel stockPilePanel;
	
	/**
	 * GUI panel for each player
	 */
	private ArrayList<PlayerPanel> playersPanels = new ArrayList<PlayerPanel>();
	
	/**
	 * Enum for end methods.
	 * @author Jeremy
	 *
	 */
	public enum EndMethod{
		Points,Rounds
	}
	
	
	/**
	 * Private constructor for singleton
	 */
	private Game() {
		playersList = new PlayersList();
	}
	
	/**
	 * Return a Game instance
	 * @return instance of Game
	 */
	public static Game getInstance(){
		if(game==null)
			game = new Game();
		return game;
	}
	
	/**
	 * Initialize the game
	 */
	public void init(){
		stockPile = new StockPile(nbOfPlayers, nbOfCardsByPlayer);
		discardPile = new DiscardPile(stockPile);
		discardPilePanel = new DiscardPilePanel(discardPile);
		stockPilePanel = new StockPilePanel(stockPile);
		setAIPlayersList();
		
		distributeCards(nbOfCardsByPlayer);
		playersPanels.clear();

		for(Player p : playersList.getPlayersList())
				playersPanels.add(new PlayerPanel(p,playersList));
		
		if(playersList.getCurrentPlayer() instanceof AI){
			playersList.getCurrentPlayer().play();
		}
		this.setChanged();
		this.notifyObservers();
	}
	
	/**
	 * Show if the game is initialized
	 * @return true if the game is initialized, false otherwise
	 */
	public boolean isInitialized(){
		if(stockPile != null && discardPile != null && discardPilePanel!=null && stockPilePanel != null && playersPanels != null && playersList != null)
			return true;
		return false;
	}
	
	/**
	 * To save the game
	 * @param file the file in which you want to save the game
	 */
	public void save(File file){
		if(file!=null){
			try {
				File f = file;
				String filePath = f.getPath();
				if(!filePath.toLowerCase().endsWith(".ces"))
				{
				    f = new File(filePath + ".ces");
				}
				ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
				oos.writeObject(discardPile);
				oos.writeObject(stockPile);
				oos.writeObject(playersList);
				oos.writeInt(nbOfPlayers);
				oos.writeInt(nbOfCardsByPlayer);
				oos.writeObject(end);
				oos.writeInt(target);
				oos.writeBoolean(changeSuit);
				oos.close();
				
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * To load a previously saved game
	 * @param file the file you want to load
	 */
	public void load(File file){
		if(file!=null){
			try {
				ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
				this.discardPile = (DiscardPile)ois.readObject();
				this.stockPile = (StockPile)ois.readObject();
				this.playersList = (PlayersList)ois.readObject();
				this.nbOfPlayers = ois.readInt();
				this.nbOfCardsByPlayer = ois.readInt();
				this.end = (EndMethod)ois.readObject();
				this. target = ois.readInt();
				this.changeSuit = ois.readBoolean();
				ois.close();
				
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
			
			discardPilePanel = new DiscardPilePanel(discardPile);
			stockPilePanel = new StockPilePanel(stockPile);
			playersPanels.clear();
			for(Player p : playersList.getPlayersList())
				playersPanels.add(new PlayerPanel(p,playersList));
		
			if(playersList.getCurrentPlayer() instanceof AI){
				playersList.getCurrentPlayer().play();
			}
		}
	}
	
	/**
	 * Reset the players list
	 */
	public void resetPlayersList(){
		playersList = new PlayersList();
	}
	
	public void setNbOfPlayers(int nbOfPlayers){
		if(nbOfPlayers > 1 && nbOfPlayers < 9)
			this.nbOfPlayers = nbOfPlayers;
	}
	
	public void setNbOfCardsByPlayer(int nbOfCardsByPlayer){
		if(nbOfCardsByPlayer>=8)
			this.nbOfCardsByPlayer = nbOfCardsByPlayer;
	}
	
	/**
	 * Add human player to the game if possible
	 * @param name the player's name
	 */
	public void addHumanPlayer(String name){
		if(playersList.size()<9)
			playersList.addPlayer(name,true);
	}
	
	public void setEndMethod(EndMethod end){
		this.end = end;
	}
	
	public void setTarget(int target){
		if(target>0)
			this.target = target;
	}
	
	/**
	 * Add AI players to match the total number of players
	 */
	public void setAIPlayersList(){
		String[] names={"James","Mary","John","Patricia","Robert","Linda","Michael","Barbara"};
		int i = 0;
		while(playersList.size()<nbOfPlayers){
			if(playersList.size()<9)
				playersList.addPlayer(names[i]);
			i++;
		}
	}
	
	/**
	 * When a player plays, this method does what needs to be done
	 * @param card The played card or null if the player draws
	 */
	public void play(Card card){
		playersList.addPlayed(playersList.getCurrentPlayer());
		if(isGameEnded()) {
			setChanged();
			notifyObservers();
		}else{
			// If the player plays a card
			if(card != null){
				if(card.getRank()==CardRank.Joker){
						try {
							playersList.getNextPlayer().addCardInHand(stockPile.draw(5));
						} catch (EmptyPileException e) {
							e.printStackTrace();
						}
						
						if(playersList.getCurrentPlayer() instanceof AI){
							playersList.getCurrentPlayer().chooseSuit();
						}
						else{
							changeSuit = true;
						}
				} else if(card.getRank()==CardRank.Eight){
					if(playersList.getCurrentPlayer() instanceof AI){
						playersList.getCurrentPlayer().chooseSuit();
					}
					else{
						changeSuit = true;
					}
				}
				else {
					if(card.getRank()==CardRank.Ace){
						this.discardPile.addAce();
					}else if(card.getRank()==CardRank.Ten){
						playersList.changeDirection();
					} else if(card.getRank()==CardRank.Seven){
						Card c = playersList.getCurrentPlayer().getRandomCard();
						playersList.nextPlayer();
						playersList.getCurrentPlayer().addCardInHand(c);
					} else if(card.getRank()==CardRank.Two){
						playersList.nextPlayer();
						try {
							playersList.getCurrentPlayer().addCardInHand(stockPile.draw(2));
						} catch (EmptyPileException e) {
							e.printStackTrace();
						}
					}
		
					playersList.nextPlayer();
				}
			}
			// If the player draws
			else{
				if(this.discardPile.getCardOnTop().getRank() == CardRank.Ace && discardPile.getConsecutiveAces()>0){
					playersList.getCurrentPlayer().draw(2*this.discardPile.getConsecutiveAces());
					discardPile.resetConsecutiveAces();
				}
				else{
					playersList.getCurrentPlayer().draw();
				}
				playersList.nextPlayer();
			}
			setChanged();
			notifyObservers();
			if(playersList.getCurrentPlayer() instanceof AI){
				playersList.getCurrentPlayer().play();
			}
		}
	}
	
	/**
	 * Check if game is ended
	 * @return true if game is ended, false otherwise
	 */
	public boolean isGameEnded() {
		// We end the Game by Rounds
		if(end == EndMethod.Rounds)
				return (playersList.getNbOfEndedRounds()==target);
		// Or by Points
		if(end == EndMethod.Points)
			for(Player p : playersList.getPlayersList())
				if(p.getPoints() >= target)
					return true;
		// Or because someone is without cards
		for(Player p : playersList.getPlayersList())
			if(p.isWithoutCards())
				return true;

		return false;
	}
	
	/**
	 * We give to each player a set of Card
	 * @param nbOfCardsByPlayer the number of cards to give to each player
	 * @throws EmptyPileException will never happen
	 */
	public void distributeCards(int nbOfCardsByPlayer) {
		for(int i = 0; i < nbOfCardsByPlayer; i++)
			for(Player p : playersList.getPlayersList())
				try {
					Card d = stockPile.draw();
					p.addCardInHand(d);
				} catch (EmptyPileException e) {
					e.printStackTrace();
				}
	}
	
	/**
	 * Check if we have to change suit and change the value of changeSuit if needed
	 * @return true if we have to change suit, false otherwise
	 */
	public boolean getChangeSuit(){
		if(changeSuit){
			changeSuit=false;
			return true;
		}
		else{
			return false;
		}
	}
	
	public DiscardPilePanel getDiscardPilePanel(){
		return discardPilePanel;
	}
	
	public StockPilePanel getStockPilePanel(){
		return stockPilePanel;
	}
	
	public ArrayList<PlayerPanel> getPlayersPanels(){
		return playersPanels;
	}
	
	public DiscardPile getDiscardPile(){
		return discardPile;
	}
	
	public StockPile getStockPile(){
		return stockPile;
	}
	
	public PlayersList getPlayersList(){
		return playersList;
	}
	
	public EndMethod getEndMethod(){
		return end;
	}
	
	public static void main(String[] args) {
		//If you want to enable the console mode, uncomment the 2 next lines and comment the third to disable the GUI
		//Console console = new Console();
		//console.start();
		new MainWindow();
	}
}
